--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 16bd39c812daf7169d2c3276d4a4cf15af669232
Parents : 2db8147
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-26T15:02:16-05:00
feat(tests): add unit tests for ConversationViewer's outbound propagation status handling, enhancing coverage for message state and method propagation
Changes
1 files changed, 146 insertions(+), 0 deletions(-)
Diff
diff --git a/tests/frontend/ConversationViewer.outboundPropagationStatus.test.js b/tests/frontend/ConversationViewer.outboundPropagationStatus.test.js
new file mode 100644
index 00000000..94b10aa9
--- /dev/null
+++ b/tests/frontend/ConversationViewer.outboundPropagationStatus.test.js
@@ -0,0 +1,146 @@
+import { mount } from "@vue/test-utils";
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import ConversationViewer from "@/components/messages/ConversationViewer.vue";
+import WebSocketConnection from "@/js/WebSocketConnection";
+import GlobalState from "@/js/GlobalState";
+
+describe("ConversationViewer outbound propagation status", () => {
+ let axiosMock;
+
+ beforeEach(() => {
+ GlobalState.config.theme = "light";
+ GlobalState.config.message_outbound_bubble_color = "#4f46e5";
+ GlobalState.config.message_waiting_bubble_color = "#e5e7eb";
+ GlobalState.config.warn_on_stranger_links = true;
+ GlobalState.detailedOutboundSendStatus = true;
+
+ WebSocketConnection.connect();
+ axiosMock = {
+ get: vi.fn().mockImplementation((url) => {
+ if (url.includes("/path")) return Promise.resolve({ data: { path: [] } });
+ if (url.includes("/stamp-info")) return Promise.resolve({ data: { lxmf_stamp_info: {} } });
+ if (url.includes("/signal-metrics")) return Promise.resolve({ data: { signal_metrics: {} } });
+ return Promise.resolve({ data: {} });
+ }),
+ post: vi.fn().mockResolvedValue({ data: {} }),
+ };
+ window.api = axiosMock;
+ });
+
+ afterEach(() => {
+ GlobalState.detailedOutboundSendStatus = false;
+ delete window.api;
+ WebSocketConnection.destroy();
+ });
+
+ const mountViewer = (extraMocks = {}) =>
+ mount(ConversationViewer, {
+ props: {
+ selectedPeer: { destination_hash: "peerhash111111111111111111111111", display_name: "Peer" },
+ myLxmfAddressHash: "myhash11111111111111111111111111",
+ conversations: [],
+ },
+ global: {
+ directives: { "click-outside": { mounted: () => {}, unmounted: () => {} } },
+ mocks: {
+ $t: (key, params) => {
+ if (params && Object.keys(params).length) {
+ return `${key}:${JSON.stringify(params)}`;
+ }
+ return key;
+ },
+ $route: { meta: {} },
+ $router: { push: vi.fn() },
+ ...extraMocks,
+ },
+ stubs: {
+ MaterialDesignIcon: true,
+ AddImageButton: true,
+ AddAudioButton: true,
+ SendMessageButton: true,
+ ConversationDropDownMenu: true,
+ PaperMessageModal: true,
+ AudioWaveformPlayer: true,
+ LxmfUserIcon: true,
+ },
+ },
+ });
+
+ it("outboundSentStatusTitle uses propagation copy when method is propagated", () => {
+ const wrapper = mountViewer();
+ expect(wrapper.vm.outboundSentStatusTitle({ method: "propagated", state: "sent" })).toBe(
+ "messages.outbound_on_propagation_node"
+ );
+ expect(wrapper.vm.outboundSentStatusTitle({ method: "direct", state: "sent" })).toBe(
+ "messages.outbound_sent_network"
+ );
+ expect(wrapper.vm.outboundSentStatusTitle(null)).toBe("");
+ });
+
+ it("outboundSendingStatusTooltip uses propagation pending strings for propagated method", () => {
+ const wrapper = mountViewer();
+ const withProgress = wrapper.vm.outboundSendingStatusTooltip({
+ method: "propagated",
+ state: "sending",
+ progress: 40,
+ });
+ expect(withProgress).toContain("messages.outbound_pending_propagation_with_progress");
+ expect(withProgress).toContain('"progress":"40"');
+
+ const pending = wrapper.vm.outboundSendingStatusTooltip({
+ method: "propagated",
+ state: "sending",
+ progress: 0,
+ });
+ expect(pending).toBe("messages.outbound_pending_propagation");
+
+ expect(
+ wrapper.vm.outboundSendingStatusTooltip({
+ method: "propagated",
+ state: "outbound",
+ })
+ ).toBe("messages.outbound_pending_propagation");
+ });
+
+ it("outboundBubbleStatusHoverTitle uses propagation pending for outbound state", () => {
+ const wrapper = mountViewer();
+ expect(
+ wrapper.vm.outboundBubbleStatusHoverTitle({
+ method: "propagated",
+ state: "outbound",
+ })
+ ).toBe("messages.outbound_pending_propagation");
+ });
+
+ it("onLxmfMessageUpdated preserves merged method when websocket sends propagated handoff", () => {
+ const wrapper = mountViewer();
+ const hash = "abc123def456789012345678901234ab";
+ wrapper.vm.chatItems = [
+ {
+ type: "lxmf_message",
+ is_outbound: true,
+ lxmf_message: {
+ hash,
+ destination_hash: "peerhash111111111111111111111111",
+ state: "sending",
+ method: "direct",
+ progress: 10,
+ content: "hi",
+ fields: {},
+ },
+ },
+ ];
+
+ wrapper.vm.onLxmfMessageUpdated({
+ hash,
+ state: "sent",
+ method: "propagated",
+ progress: 100,
+ });
+
+ const row = wrapper.vm.chatItems[0].lxmf_message;
+ expect(row.state).toBe("sent");
+ expect(row.method).toBe("propagated");
+ expect(row.progress).toBe(100);
+ });
+});
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────